home *** CD-ROM | disk | FTP | other *** search
/ Developer Helper 1: Phil & Dave's Excellent CD / Excellent CD HFS.raw / Moof / Goodies / HyperCard Goodies / Serial Toolkit / Source Code / XModem.p < prev   
Text File  |  1988-11-18  |  13KB  |  521 lines

  1. (*
  2.     XModem command,folderName,fileName -- Send or receive a file using the XModem protocol. If
  3.         command is "receive", then receive a file and name it as specified. If the command is "send",
  4.         then send the named file. The folder and file name are separate so that the file can be downloaded
  5.         into the right folder before knowing the proper name. Assume the serial port has already been opened.
  6.  
  7.     This XModem implementation handles normal XModem and CRC XModem. It does not know anything
  8.     about the format of the data being sent or received -- i.e., it doesn't know anything about MacBinary.
  9.  
  10.     It sends very fast. Receive could be sped up some (it should compute the CRC as it's receiving, not
  11.     afterward), but it isn't all that bad either.
  12.  
  13.     To compile and link this file using Macintosh Programmer's Workshop,
  14.  
  15.         pascal -w XModem.p
  16.         link -m ENTRYPOINT -o HyperCommands -rt XCMD=7036 -sn Main=XModem ∂
  17.             XModem.p.o "{MPW}"Libraries:interface.o "{MPW}"PLibraries:PasLib.o
  18.  
  19.     © Copyright 1987,88 by Apple Computer, Inc.
  20.  
  21.     Initial coding 9/87 by Harry R. Chesley.
  22. *)
  23.  
  24. {$R-}
  25.  
  26. {$S XModem }     { Segment name must be the same as the command name. }
  27.  
  28. unit DummyUnit;
  29.  
  30. interface
  31.  
  32. uses MemTypes, QuickDraw, OSIntf, ToolIntf, HyperXCmd;
  33.  
  34. procedure EntryPoint(paramPtr: XCmdPtr);
  35.     
  36. implementation
  37.  
  38. type
  39.  
  40. Str31 = String[31];
  41.  
  42. procedure XModem(paramPtr: XCmdPtr); forward;
  43.  
  44. procedure EntryPoint(paramPtr: XCmdPtr);
  45.  
  46.     begin
  47.         XModem(paramPtr);
  48.     end;
  49.  
  50. {«XModem(paramPtr: XCmdPtr)»}
  51.  
  52. procedure XModem(paramPtr: XCmdPtr);
  53.  
  54.     const TEMPFILENAME = '*** recvXModemTemp ***';
  55.         RETRY = 5;
  56.         RECSIZE = 128;
  57.         EOT = 4;
  58.         CRC = ord('C');
  59.         ACK = 6;
  60.         NAK = $15;
  61.         CAN = $18;
  62.         SOH = 1;
  63.  
  64.     var theFolder: str255;
  65.         theFile: Str255;
  66.         tempFile: str255;
  67.         cmd: Str255;                                                { The command. }
  68.         dummy: OSErr;
  69.         fileRef: integer;                                            { Input or output file reference number. }
  70.         useCRC: boolean;                                            { True if we're using CRCs. }
  71.         crcAccum, checkSum: longInt;                        { The CRC or checksum. }
  72.         buffer: array [1..RECSIZE] of SignedByte;        { The I/O buffer. }
  73.         recNum: longInt;                                            { The current record number. }
  74.  
  75.     {$I XCmdGlue.inc}
  76.  
  77.     procedure Fail(errMsg: Str255);
  78.         { Set theResult and quit. }
  79.         begin
  80.             paramPtr^.returnValue := PasToZero(errMsg);
  81.             exit(XModem);
  82.         end;
  83.  
  84.     {$I SPortUtil.inc}
  85.  
  86. {«Miscellaneous Routines»    Miscellaneous Routines }
  87.  
  88.     function byteAvailable: boolean;
  89.         { Returns true if a serial port input byte is available. }
  90.  
  91.         var l: longInt;
  92.  
  93.         begin
  94.             if SerGetBuf(ThisSPort.portInDev,l) <> noErr then Fail('SerGetBuf failed');
  95.             byteAvailable :=  l > 0;
  96.         end;
  97.  
  98.     function readOne: SignedByte;
  99.         { Reads a byte from the serial port. }
  100.  
  101.         var l: longInt;
  102.             theByte: SignedByte;
  103.  
  104.         begin
  105.             l := 1;
  106.             if FSRead(ThisSPort.portInDev,l,@theByte) <> noErr then Fail('FSRead failed');
  107.             readOne := theByte;
  108.         end;
  109.  
  110.     procedure tx(b: SignedByte);
  111.         { Transmits a byte to to serial port. }
  112.  
  113.         var theByte: SignedByte;
  114.             l: longInt;
  115.  
  116.         begin
  117.             theByte := b;
  118.             l := 1;
  119.             if FSWrite(ThisSPort.portOutDev,l,@theByte) <> noErr then Fail('FSWrite failed');
  120.         end;
  121.  
  122.     function swait: SignedByte;
  123.         { Waits up to 2 seconds for a byte from the serial port. }
  124.  
  125.         var stoppingTime: longInt;
  126.             theByte: SignedByte;
  127.             l: longInt;
  128.  
  129.         begin
  130.             stoppingTime := TickCount + 120;
  131.             while TickCount < stoppingTime do
  132.                 if byteAvailable then
  133.                     begin
  134.                         swait := readOne;
  135.                         exit(swait);
  136.                     end;
  137.             swait := 0;
  138.         end;
  139.  
  140.     procedure clearInput;
  141.         { Clear out any residual characters on the input port. }
  142.  
  143.         var theByte: SignedByte;
  144.  
  145.         begin
  146.             while byteAvailable do theByte := readOne;
  147.         end;
  148.  
  149.     procedure sleep(ticks: longInt);
  150.         { Wait for ticks 1/60ths of a second. }
  151.  
  152.         var finalTime: longInt;
  153.  
  154.         begin
  155.             finalTime := TickCount + ticks;
  156.             repeat until TickCount >= finalTime;
  157.         end;
  158.  
  159. {«CRC/Checksum Routines»    CRC/Checksum Routines }
  160.  
  161.     procedure clrCrc;
  162.         { Clear out the CRC/checksum. }
  163.  
  164.         begin
  165.             crcAccum := 0;
  166.             checkSum := 0;
  167.         end;
  168.  
  169.     procedure updCrc(b: SignedByte);
  170.         { Process one more byte into the CRC/Checksum. }
  171.  
  172.         var i: integer;
  173.             flag: longInt;
  174.  
  175.         begin
  176.             if useCRC then
  177.                 begin
  178.                     for i := 7 downto 0 do
  179.                         begin
  180.                             flag := BitAnd(crcAccum,$08000);
  181.                             crcAccum := BitShift(crcAccum,1);
  182.                             if BitAnd(BitShift(1,i),b) <> 0 then crcAccum := BitOr(crcAccum,1);
  183.                             if flag <> 0 then crcAccum := BitXor(crcAccum,$01021);
  184.                         end;
  185.                 end
  186.             else checkSum := checkSum + BitAnd(b,$FF);
  187.         end;
  188.  
  189. {«sendFile»}
  190.  
  191.     procedure sendFile;
  192.         { Open and send a file. }
  193.  
  194.         var didSend: boolean;        { True if send was successfull. }
  195.  
  196.         function send: boolean;
  197.             { Send the file (once it's been opened). }
  198.  
  199.             var i: integer;
  200.  
  201.             procedure Fail(errMsg: Str255);
  202.                 { Set theResult and quit. }
  203.  
  204.                 var dummy: OSErr;
  205.  
  206.                 begin
  207.                     dummy := FSClose(fileRef);
  208.                     paramPtr^.returnValue := PasToZero(errMsg);
  209.                     exit(XModem);
  210.                 end;
  211.  
  212.             function waitCan(t: longInt): SignedByte;
  213.                 { Wait up to t ticks for a byte from the serial port. }
  214.     
  215.                 var stopTime: longInt;
  216.                     theByte: SignedByte;
  217.     
  218.                 begin
  219.                     stopTime := TickCount+t;
  220.                     while TickCount < stopTime do
  221.                         if byteAvailable then
  222.                             begin
  223.                                 theByte := readOne;
  224.                                 if theByte = CAN then exit(send);
  225.                                 waitCan := theByte;
  226.                                 exit(waitCan);
  227.                             end;
  228.                     waitCan := 0;
  229.                 end;
  230.     
  231.             procedure synch;
  232.                 { Synchronize our ACKs/NAKs and figure out if we're doing CRCs or checksums. }
  233.     
  234.                 var i: integer;
  235.                     theByte: SignedByte;
  236.     
  237.                 begin
  238.                     clearInput;
  239.                     { Try up to ten times to synchronize. }
  240.                     for i := 1 to 10 do
  241.                         begin
  242.                             { Get a character. }
  243.                             theByte := waitCan(600);
  244.                             { Check for NAK — normal, checksum XModem. }
  245.                             if theByte = NAK then
  246.                                 begin
  247.                                     useCRC := false;
  248.                                     exit(synch);
  249.                                 end;
  250.                             { Check for CRC — CRC style XModem. }
  251.                             if theByte = CRC then
  252.                                 begin
  253.                                     useCRC := true;
  254.                                     exit(synch);
  255.                                 end;
  256.                         end;
  257.                     { Didn't find it. Give up. }
  258.                     Fail('could not synchronize');
  259.                 end;
  260.  
  261.             function fillBuf: boolean;
  262.                 { Get a bufferfull from disk. }
  263.  
  264.                 var l: longInt;
  265.  
  266.                 begin
  267.                     l := RECSIZE;
  268.                     if (FSRead(fileRef,l,@buffer) <> noErr) or (l = 0) then fillBuf := false
  269.                     else fillBuf := true;
  270.                 end;
  271.  
  272.             procedure txRec;
  273.                 { Transmit one record. }
  274.  
  275.                 var i, j: integer;
  276.                     theByte: SignedByte;
  277.  
  278.                 begin
  279.                     clearInput;
  280.                     { Try to send the record up to 25 times. }
  281.                     for i := 1 to 25 do
  282.                         begin
  283.                             { Send the record header. }
  284.                             tx(SOH);
  285.                             tx(BitAnd(recNum,$FF));
  286.                             tx(BitAnd(BitXor(recNum,$FF),$FF));
  287.                             { Send the record body, figuring the CRC as we go. }
  288.                             clrCrc;
  289.                             for j := 1 to RECSIZE do
  290.                                 begin
  291.                                     tx(buffer[j]);
  292.                                     updCrc(buffer[j]);
  293.                                 end;
  294.                             updCrc(0); updCrc(0);
  295.                             { Send the CRC. }
  296.                             if useCrc then
  297.                                 begin
  298.                                     tx(BitAnd(BitShift(crcAccum,-8),$FF));
  299.                                     tx(BitAnd(crcAccum,$FF));
  300.                                 end
  301.                             else tx(BitAnd(checkSum,$FF));
  302.                             { Wait for the ACK. }
  303.                             if waitCan(600) = ACK then
  304.                                 begin
  305.                                     { If we succeeded, increment the record number. }
  306.                                     recNum := recNum+1;
  307.                                     exit(txRec);
  308.                                 end;
  309.                         end;
  310.                         Fail('retries exceeded')
  311.                 end;
  312.  
  313.             begin
  314.                 { Initialize and synchronize with the other side. }
  315.                 send := false;
  316.                 recNum := 1;
  317.                 synch;
  318.                 { Send the file, one record at a time. }
  319.                 while fillBuf do txRec;
  320.                 { Make sure he knows we're all done. }
  321.                 for i := 1 to 100 do
  322.                     begin
  323.                         tx(EOT);
  324.                         if waitCan(60) = ACK then leave;
  325.                     end;
  326.                 send := true;
  327.             end;
  328.  
  329.         begin
  330.             { Open the file to be sent. }
  331.             if FSOpen(theFile,0,fileRef) <> noErr then Fail('no such file');
  332.     
  333.             { Send the file. }
  334.             didSend := send;
  335.  
  336.             { Close the file. }
  337.             if FSClose(fileRef) <> noErr then Fail('FSClose failed');
  338.             if not didSend then Fail('send aborted');
  339.         end;
  340.  
  341. {«receiveFile»}
  342.  
  343.     procedure receiveFile;
  344.         { Create and receive one file. }
  345.  
  346.         var didReceive: boolean;        { True if the receive was successfull. }
  347.                 i: integer;
  348.                 lastColon: integer;
  349.                 volName: str255;
  350.  
  351.         function receive: boolean;
  352.             { Receive the file (once it has been created on disk). }
  353.     
  354.             var i: integer;
  355.                 theByte: SignedByte;
  356.                 response: SignedByte;
  357.                 r1, r2: longInt;
  358.                 crcHi, crcLo: longInt;
  359.                 l: longInt;
  360.  
  361.             procedure Fail(errMsg: Str255);
  362.                 { Set theResult and quit. }
  363.  
  364.                 var dummy: OSErr;
  365.  
  366.                 begin
  367.                     dummy := FSClose(fileRef);
  368.                     paramPtr^.returnValue := PasToZero(errMsg);
  369.                     exit(XModem);
  370.                 end;
  371.  
  372.             begin
  373.                 { Assume we'll fail: }
  374.                 receive := false;
  375.     
  376.                 { Dump any previous residue: }
  377.                 clearInput;
  378.     
  379.                 { Assume we're doing CRC XModem: }
  380.                 response := CRC;
  381.                 useCRC := true;
  382.     
  383.                 { Start at record one: }
  384.                 recNum := 1;
  385.     
  386.                 { Read in the file: }
  387.                 while true do
  388.                     begin
  389.                         { Synch up: }
  390.                         clearInput;
  391.                         for i := 1 to 10*RETRY do
  392.                             begin
  393.                                 { Let him know what happened last time (or NAK him the first time). }
  394.                                 tx(response);
  395.                                 { Get a byte. }
  396.                                 theByte := swait;
  397.                                 { Check or start-of-record. }
  398.                                 if theByte = SOH then leave;
  399.                                 { Check for end-of-file. }
  400.                                 if theByte = EOT then
  401.                                     begin
  402.                                         tx(ACK);
  403.                                         receive := true;
  404.                                         exit(receive);
  405.                                     end;
  406.                                 { Check for manual cancel. }
  407.                                 if theByte = CAN then exit(receive);
  408.                                 { If we've tried CRCs for a while, try checksums now. }
  409.                                 if i = 5*RETRY then
  410.                                     begin
  411.                                         if response = CRC then
  412.                                             begin
  413.                                                 response := NAK;
  414.                                                 useCRC := false;
  415.                                             end
  416.                                         else leave;
  417.                                     end;
  418.                             end;
  419.                         { If this isn't another block, it must be that we've run out of retries. }
  420.                         if theByte <> SOH then exit(receive);
  421.                         { Receive the record number (and its complement). }
  422.                         r1 := BitAnd(swait,$FF); r2 := BitAnd(swait,$FF);
  423.                         { Receive the record. }
  424.                         for i := 1 to RECSIZE do buffer[i] := swait;
  425.                         { Receive the CRC/checksum. }
  426.                         if useCRC then crcHi := BitAnd(swait,$FF);
  427.                         crcLo := BitAnd(swait,$FF);
  428.                         { Assume this one'll be bad. }
  429.                         response := NAK;
  430.                         { Check for bad record number. }
  431.                         if BitAnd(BitNot(r1),$FF) <> r2 then cycle;
  432.                         { Check for bad CRC/checksum. }
  433.                         clrCrc;
  434.                         for i := 1 to RECSIZE do updCrc(buffer[i]);
  435.                         updCrc(0); updCrc(0);
  436.                         if useCRC then
  437.                             begin
  438.                                 if (crcLo + BitShift(crcHi,8)) <> BitAnd(crcAccum,$0FFFF) then
  439.                                     begin
  440.                                         { Give things time to settle out. }
  441.                                         sleep(120);
  442.                                         cycle;
  443.                                     end;
  444.                             end
  445.                         else if crcLo <> BitAnd(checkSum,$FF) then
  446.                             begin
  447.                                 { Give things time to settle out. }
  448.                                 sleep(120);
  449.                                 cycle;
  450.                             end;
  451.                         { Check if this is a record we've already received. }
  452.                         if r1 = BitAnd(recNum-1,$FF) then
  453.                             begin
  454.                                 { If it is... }
  455.                                 response := ACK;
  456.                                 cycle;
  457.                             end;
  458.                         { Check if this record is WAY out of sequence. }
  459.                         if r1 <> BitAnd(recNum,$FF) then Fail('fatal record sequencing error');
  460.                         { Increment our expected record number. }
  461.                         recNum := recNum+1;
  462.                         { Write the record to disk. }
  463.                         l := RECSIZE;
  464.                         if FSWrite(fileRef,l,@buffer) <> noErr then Fail('FSWrite failed');
  465.                         response := ACK;
  466.                     end;
  467.             end;
  468.  
  469.         begin
  470.             { Create the temporary input file (will be renamed later to the real name if we make it OK). }
  471.             dummy := FSDelete(tempFile,0);
  472.             if Create(tempFile,0,'????','????') <> noErr then Fail('could not create temp file');
  473.             if FSOpen(tempFile,0,fileRef) <> noErr then Fail('FSOpen failed on temp file');
  474.     
  475.             { Receive the file: }
  476.             didReceive := receive;
  477.  
  478.             { Close the file, and rename the temporary file to the permanent name. }
  479.             if FSClose(fileRef) <> noErr then Fail('FSClose failed');
  480.             if didReceive then
  481.                 begin
  482.                     dummy := FSDelete(theFile,0);
  483.                     if Rename(tempFile,0,theFile) <> noErr then Fail('Rename failed');
  484.                 end
  485.             else
  486.                 begin
  487.                     if FSDelete(tempFile,0) <> noErr then Fail('FSDelete failed');
  488.                     Fail('receive aborted');
  489.                 end;
  490.             lastColon := 0;
  491.             for i := 1 to length(theFile) do
  492.                 if theFile[i] = ':' then lastColon := i;
  493.             if lastColon = 0 then volName := ''
  494.             else volName := Copy(theFile,1,lastColon);
  495.             if FlushVol(@volName,0) <> noErr then Fail('FlushVol failed');
  496.         end;
  497.  
  498. {«Main Program»    Main Program }
  499.  
  500.     begin
  501.         { Check the parameter count. }
  502.         if paramPtr^.paramCount <> 3 then Fail('parameter count is not 3');
  503.  
  504.         { Get the parameters. }
  505.         GetStrParm(1,cmd);                { First parameter is the command. }
  506.         GetStrParm(2,theFolder);        { Second parameter is the folder to put the file in. }
  507.         tempFile := Concat(theFolder,TEMPFILENAME);
  508.         GetStrParm(3,theFile);            { Third parameter is file name to send/receive. }
  509.         theFile := Concat(theFolder,theFile);
  510.  
  511.         SetUpSPortGlobals;
  512.         EnsureOpenPort;
  513.  
  514.         { Find out what command we're doing and do it. }
  515.         if StringEqual(cmd,'send') then sendFile
  516.         else if StringEqual(cmd,'receive') then receiveFile
  517.         else Fail('invalid command');
  518.     end;
  519.  
  520. end.
  521.